package com.youdianstar.app.util.biometric;

import android.content.Context;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;

import androidx.annotation.RequiresApi;

import com.google.gson.Gson;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;


/**
 * File: CryptographyHelper
 * Author: 82149 Create: 2021/7/27 12:21
 * Changes (from 2021/7/27)
 * --------------------------------------------------
 * description: 生物识别数据读取
 * ---------------------------------------------------
 */
public class CryptographyHelper implements ICryptographyManager {
    public static String BIOMETRIC_KEY = "HERO_BIO_KEY";
    public static String SHARED_PREFS_FILENAME = "biometric_prefs";
    public static String CIPHERTEXT_WRAPPER = "ciphertext_wrapper";

    private final String ANDROID_KEYSTORE = "AndroidKeyStore";
    private final String ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM;//KeyProperties.BLOCK_MODE_CBC
    private final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE;//KeyProperties.ENCRYPTION_PADDING_PKCS7
    private final String ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;

    public static CryptographyHelper mHelper = null;

    public static synchronized CryptographyHelper getInstance(){
        if (null == mHelper) {
            synchronized (CryptographyHelper.class) {
                mHelper = new CryptographyHelper();
            }
        }
        return mHelper;
    }

    private Cipher getCipher(){
        String transformation = ENCRYPTION_ALGORITHM+"/"+ENCRYPTION_BLOCK_MODE+"/"+ENCRYPTION_PADDING;
        try {
            return Cipher.getInstance(transformation);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private SecretKey createKey(String keyName) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        KeyGenParameterSpec.Builder paramsBuilder = new KeyGenParameterSpec
                .Builder(keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);

        int KEY_SIZE = 256;
        paramsBuilder.setBlockModes(ENCRYPTION_BLOCK_MODE)
                .setEncryptionPaddings(ENCRYPTION_PADDING)
                .setKeySize(KEY_SIZE)
                .setUserAuthenticationRequired(true);

        KeyGenParameterSpec keyGenParams = paramsBuilder.build();
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
        keyGenerator.init(keyGenParams);
        return keyGenerator.generateKey();
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private SecretKey getOrCreateSecretKey(String keyName) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, NoSuchProviderException, InvalidAlgorithmParameterException {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
        keyStore.load(null);
        SecretKey storeSecret = ((SecretKey)keyStore.getKey(keyName, null));
        if (null!=storeSecret)
            return storeSecret;
        return createKey(keyName);
    }

    private void deleteKey(String keyName){
        try {
            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
            keyStore.deleteEntry(keyName);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public Cipher getInitCipherForEncryption(@NotNull String keyName) {
        Cipher cipher = null;
        try {
            cipher = getCipher();
            SecretKey secretKey = getOrCreateSecretKey(keyName);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        }catch (Exception e){
            e.printStackTrace();
            deleteKey(keyName);
            try {
                SecretKey newScr = createKey(keyName);
                cipher.init(Cipher.ENCRYPT_MODE, newScr);
            } catch (Exception ce){
                ce.printStackTrace();
            }
        }
        return cipher;
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public Cipher getInitCipherForDecryption(@NotNull String keyName, @NotNull byte[] initializationVector) {
        Cipher cipher = null;
        try {
            cipher = getCipher();
            SecretKey secretKey = getOrCreateSecretKey(keyName);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, initializationVector));
        }catch (Exception e){
            e.printStackTrace();
            deleteKey(keyName);
            try {
                SecretKey newScr = createKey(keyName);
                cipher.init(Cipher.ENCRYPT_MODE, newScr);
            } catch (Exception ce){
                ce.printStackTrace();
            }
        }
        return cipher;
    }


    @Override
    public CipherTextWrapper encryptData(@NotNull String plaintext, @NotNull Cipher cipher) {
        try {
            byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            return new CipherTextWrapper(ciphertext, cipher.getIV());
        }catch (Exception e){
            return null;
        }
    }


    @Override
    public String decryptData(@NotNull byte[] ciphertext, @NotNull Cipher cipher) {
        try {
            byte[] plaintext = cipher.doFinal(ciphertext);
            return new String(plaintext,StandardCharsets.UTF_8);
        }catch (Exception e){
            return null;
        }
    }

    @Override
    public void persistCipherTextWrapperToSP(@NotNull CipherTextWrapper ciphertextWrapper, @NotNull Context context, @NotNull String filename, int mode, @NotNull String prefKey) {
        String json = new Gson().toJson(ciphertextWrapper);
        context.getSharedPreferences(filename, mode).edit().putString(prefKey, json).apply();
    }

    /**
     * 保存加密数据
     * @param context app base context
     */
    public void persistCipherTextWrapper(@NotNull CipherTextWrapper ciphertextWrapper, @NotNull Context context){
        persistCipherTextWrapperToSP(ciphertextWrapper,context, SHARED_PREFS_FILENAME, Context.MODE_PRIVATE, CIPHERTEXT_WRAPPER);
    }

    @Nullable
    @Override
    public CipherTextWrapper getCipherTextWrapperFromSP(@NotNull Context context, @NotNull String filename, int mode, @NotNull String prefKey) {
        String json = context.getSharedPreferences(filename, mode).getString(prefKey, null);
        return new Gson().fromJson(json,CipherTextWrapper.class);
    }

    /**
     * 获取加密数据
     * @param context app base context
     */
    public CipherTextWrapper getCipherTextWrapper(@NotNull Context context){
        return getCipherTextWrapperFromSP(context,SHARED_PREFS_FILENAME, Context.MODE_PRIVATE,CIPHERTEXT_WRAPPER);
    }

    @Override
    public void cleanCipherTextWrapper(@NotNull Context context, @NotNull String filename, int mode, @NotNull String prefKey) {
        context.getSharedPreferences(filename, mode).edit().putString(prefKey,"").apply();
    }

    /**
     * 清空加密数据
     * @param context app base context
     */
    public void cleanCipherTextWrapper(@NotNull Context context){
        cleanCipherTextWrapper(context,SHARED_PREFS_FILENAME, Context.MODE_PRIVATE, CIPHERTEXT_WRAPPER);
    }

    public static class CipherTextWrapper {
        public byte[] ciphertext;
        public byte[] initVector;

        public CipherTextWrapper(byte[] ciphertext, byte[] initVector) {
            this.ciphertext = ciphertext;
            this.initVector = initVector;
        }
    }



}
